package com.kai.service.util;

import cn.hutool.core.date.DateUtil;
import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.kai.service.data.dto.upload.SingleFileUploadDto;
import com.kai.service.data.dto.upload.UploadFileInfoDto;
import com.kai.service.data.dto.upload.UploadObjectDto;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class AmazonS3Util {

    @Autowired
    private AmazonS3 amazonS3;

    private static final char UNIX_SEPARATOR = '/';

    private static final Integer EXPIRATION_TIME = 10 * 60 * 1000;

    @Value("${minio.bucket:}")
    private String bucket;

    @Value("${minio.endpoint:}")
    private String endpoint;


    /**
     * 初始化分片上传
     *
     * @param fileName 文件名
     * @return
     */
    public UploadObjectDto initiateMultipartUpload(String fileName) {
        String contentType = MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        String objectKey = this.getObjectKey(contentType, fileName);

        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        InitiateMultipartUploadResult result = amazonS3.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucket, objectKey, objectMetadata));

        return new UploadObjectDto()
                .setBucket(result.getBucketName())
                .setUploadId(result.getUploadId())
                .setObjectKey(result.getKey());
    }

    /**
     * 查看文件是否存在
     *
     * @param bucket 桶
     * @param objectKey 文件key
     * @return ture-文件存在，false-文件不存在
     */
    public Boolean doesObjectExist(String bucket, String objectKey) {
        return amazonS3.doesObjectExist(bucket, objectKey);
    }

    /**
     * 查看已上传分片列表
     *
     * @param uploadId 上传id
     * @param bucket 桶
     * @param objectKey 文件key
     * @return 已经上传分片文件信息
     */
    public List<UploadFileInfoDto> listParts(String uploadId, String bucket, String objectKey) {
        return amazonS3.listParts(new ListPartsRequest(bucket, objectKey, uploadId))
                .getParts().stream().map(partSummary -> new UploadFileInfoDto()
                        .setETag(partSummary.getETag())
                        .setSize(partSummary.getSize())
                        .setLastModified(partSummary.getLastModified())
                        .setPartNumber(partSummary.getPartNumber()))
                .collect(Collectors.toList());
    }

    /**
     * 获取预签名上传url
     *
     * @param uploadId   上传id
     * @param bucket     桶
     * @param objectKey  文件key
     * @param partNumber 分片数
     * @return 预签名上传url
     */
    public String genPreSignUploadUrl(String uploadId, String bucket, String objectKey, Integer partNumber) {
        Date expireDate = DateUtil.offsetMillisecond(new Date(), EXPIRATION_TIME);
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, objectKey)
                .withExpiration(expireDate).withMethod(HttpMethod.PUT);
        request.addRequestParameter("uploadId", uploadId);
        request.addRequestParameter("partNumber", partNumber.toString());

        return amazonS3.generatePresignedUrl(request).toString();
    }

    /**
     * 合并分片
     *
     * @param uploadId 上传id
     * @param bucket 桶
     * @param objectKey 文件key
     * @param uploadFileInfos 已上传分片文件信息
     */
    public void completeMultipartUpload(String uploadId, String bucket, String objectKey, List<UploadFileInfoDto> uploadFileInfos) {
        CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest()
                .withUploadId(uploadId)
                .withKey(objectKey)
                .withBucketName(bucket)
                .withPartETags(uploadFileInfos.stream().map(uploadFileInfo ->
                                new PartETag(uploadFileInfo.getPartNumber(), uploadFileInfo.getETag()))
                        .collect(Collectors.toList()));
        amazonS3.completeMultipartUpload(request);
    }

    /**
     * 单文件上传
     *
     * @param fileName 文件名
     * @return 单文件上传信息
     */
    public SingleFileUploadDto singleFileUpload(String fileName, String objectKey) {
        String contentType = MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM).toString();
        if (StringUtils.isBlank(objectKey)) {
            objectKey = this.getObjectKey(contentType, fileName);
        }

        Date expireDate = DateUtil.offsetMillisecond(new Date(), EXPIRATION_TIME);
        String presignedUrl = amazonS3.generatePresignedUrl(
                new GeneratePresignedUrlRequest(bucket, objectKey)
                        .withExpiration(expireDate)
                        .withMethod(HttpMethod.PUT))
                .toString();

        return new SingleFileUploadDto()
                .setPresignedUrl(presignedUrl)
                .setBucket(bucket)
                .setObjectKey(objectKey);
    }

    /**
     * 获取完整url
     *
     * @param bucket    桶
     * @param objectKey 文件key
     * @return 完整的文件url
     */
    public String completeUrl(String bucket, String objectKey) {
        return endpoint + UNIX_SEPARATOR + bucket + UNIX_SEPARATOR + objectKey;
    }

    /**
     * 获取文件名称
     *
     * @param originalName 原始文件名称
     * @return 文件名 格式（yyyyMMddHHmmss + 6位随机字符 + 文件后缀）
     */
    private String getFileName(String originalName) {
        return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) +
                KeyGenerator.generateKey(6) +
                FilenameUtils.EXTENSION_SEPARATOR +
                FilenameUtils.getExtension(originalName);
    }

    /**
     * 根据文件类型获取储存目录
     *
     * @param contentType 文件类型
     * @return 文件储存的目录
     */
    private String getDirectoryByContentType(String contentType) {
        if (StringUtils.isBlank(contentType)) {
            return "";
        }
        switch (contentType) {
            case "image/jpeg":
                return "images/";
            default:
                return "other/";
        }
    }

    /**
     * 获取文件key
     *
     * @param contentType 文件类型
     * @param originalName 文件名
     * @return 文件key
     */
    private String getObjectKey(String contentType, String originalName) {
        return this.getDirectoryByContentType(contentType) + this.getFileName(originalName);
    }
}
